package guice.boot.archive.container.model;

import guice.boot.archive.bootstrap.MainMethodRunner;
import guice.boot.archive.common.util.*;
import guice.boot.archive.container.service.ContainerHolder;
import guice.boot.archive.exception.ArkRuntimeException;
import guice.boot.archive.spi.constant.Constants;
import guice.boot.archive.spi.event.biz.*;
import guice.boot.archive.spi.model.Biz;
import guice.boot.archive.spi.model.BizState;
import guice.boot.archive.spi.service.PriorityOrdered;
import guice.boot.archive.spi.service.biz.BizManagerService;
import guice.boot.archive.spi.service.event.EventService;

import java.io.File;
import java.net.URL;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Ark Biz Standard Model
 */
public class BizModel implements Biz {

    private String bizName;

    private String bizVersion;

    private BizState bizState;

    private String mainClass;

    private String webContextPath;

    private URL[] urls;

    private ClassLoader classLoader;

    private Map<String, String> attributes = new ConcurrentHashMap<>();

    private int priority = PriorityOrdered.DEFAULT_PRECEDENCE;

    private Set<String> denyImportPackages;

    private Set<String> denyImportPackageNodes = new HashSet<>();

    private Set<String> denyImportPackageStems = new HashSet<>();

    private Set<String> denyImportClasses;

    private Set<String> denyImportResources = new HashSet<>();

    private Set<String> injectPluginDependencies = new HashSet<>();
    private Set<String> injectExportPackages = new HashSet<>();

    private Set<String> denyPrefixImportResourceStems = new HashSet<>();

    private Set<String> denySuffixImportResourceStems = new HashSet<>();

    private File bizTempWorkDir;

    public BizModel setClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
        return this;
    }

    public BizModel setAttribute(String key, String val) {
        attributes.put(key, val);
        return this;
    }

    public Set<String> getInjectExportPackages() {
        return injectExportPackages;
    }

    public BizModel setInjectExportPackages(String injectExportPackages) {
        this.injectExportPackages = StringUtils.strToSet(injectExportPackages,
                Constants.MANIFEST_VALUE_SPLIT);
        return this;
    }

    @Override
    public String getBizName() {
        return bizName;
    }

    public BizModel setBizName(String bizName) {
        AssertUtils.isFalse(StringUtils.isEmpty(bizName), "Biz Name must not be empty!");
        this.bizName = bizName;
        return this;
    }

    @Override
    public String getBizVersion() {
        return bizVersion;
    }

    public BizModel setBizVersion(String bizVersion) {
        AssertUtils.isFalse(StringUtils.isEmpty(bizVersion), "Biz Version must not be empty!");
        this.bizVersion = bizVersion;
        return this;
    }

    @Override
    public String getIdentity() {
        return BizIdentityUtils.generateBizIdentity(this);
    }

    @Override
    public String getMainClass() {
        return mainClass;
    }

    public BizModel setMainClass(String mainClass) {
        AssertUtils.isFalse(StringUtils.isEmpty(mainClass), "Biz Main Class must not be empty!");
        this.mainClass = mainClass;
        return this;
    }

    @Override
    public URL[] getClassPath() {
        return urls;
    }

    public BizModel setClassPath(URL[] urls) {
        this.urls = urls;
        return this;
    }

    @Override
    public int getPriority() {
        return priority;
    }

    public BizModel setPriority(String priority) {
        this.priority = (priority == null ? PriorityOrdered.DEFAULT_PRECEDENCE : Integer.valueOf(priority));
        return this;
    }

    @Override
    public ClassLoader getBizClassLoader() {
        return classLoader;
    }

    @Override
    public Set<String> getDenyImportPackages() {
        return denyImportPackages;
    }

    public BizModel setDenyImportPackages(String denyImportPackages) {
        this.denyImportPackages = StringUtils.strToSet(denyImportPackages,
                Constants.MANIFEST_VALUE_SPLIT);
        ParseUtils.parsePackageNodeAndStem(this.denyImportPackages, this.denyImportPackageStems,
                this.denyImportPackageNodes);
        return this;
    }

    @Override
    public Set<String> getDenyImportPackageNodes() {
        return denyImportPackageNodes;
    }

    @Override
    public Set<String> getDenyImportPackageStems() {
        return denyImportPackageStems;
    }

    @Override
    public Set<String> getDenyImportClasses() {
        return denyImportClasses;
    }

    public BizModel setDenyImportClasses(String denyImportClasses) {
        this.denyImportClasses = StringUtils.strToSet(denyImportClasses,
                Constants.MANIFEST_VALUE_SPLIT);
        return this;
    }

    @Override
    public Set<String> getDenyImportResources() {
        return denyImportResources;
    }

    public BizModel setDenyImportResources(String denyImportResources) {
        ParseUtils.parseResourceAndStem(
                StringUtils.strToSet(denyImportResources, Constants.MANIFEST_VALUE_SPLIT),
                this.denyPrefixImportResourceStems, denySuffixImportResourceStems,
                this.denyImportResources);
        return this;
    }

    public Set<String> getInjectPluginDependencies() {
        return injectPluginDependencies;
    }

    public BizModel setInjectPluginDependencies(Set<String> injectPluginDependencies) {
        this.injectPluginDependencies = injectPluginDependencies;
        return this;
    }

    @Override
    public Set<String> getDenyPrefixImportResourceStems() {
        return denyPrefixImportResourceStems;
    }

    @Override
    public Set<String> getDenySuffixImportResourceStems() {
        return denySuffixImportResourceStems;
    }

    @Override
    public void start(String[] args) throws Throwable {
        AssertUtils.isTrue(bizState == BizState.RESOLVED, "BizState must be RESOLVED");
        if (mainClass == null) {
            throw new ArkRuntimeException(String.format("biz: %s has no main method", getBizName()));
        }
        ClassLoader oldClassLoader = ClassLoaders.pushContextClassLoader(this.classLoader);
        EventService eventService = ContainerHolder.getContainer().getService(
                EventService.class);
        try {
            eventService.sendEvent(new BeforeBizStartupEvent(this));
            resetProperties();
            MainMethodRunner mainMethodRunner = new MainMethodRunner(mainClass, args);
            mainMethodRunner.invoke();
            // this can trigger health checker handler
            eventService.sendEvent(new AfterBizStartupEvent(this));
        } catch (Throwable e) {
            bizState = BizState.BROKEN;
            throw e;
        } finally {
            ClassLoaders.popContextClassLoader(oldClassLoader);
        }
        BizManagerService bizManagerService = ContainerHolder.getContainer().getService(BizManagerService.class);
        if (bizManagerService.getActiveBiz(bizName) == null) {
            bizState = BizState.ACTIVATED;
        } else {
            bizState = BizState.DEACTIVATED;
        }
    }

    @Override
    public void stop() {
        AssertUtils.isTrue(bizState == BizState.ACTIVATED || bizState == BizState.DEACTIVATED
                        || bizState == BizState.BROKEN,
                "BizState must be ACTIVATED, DEACTIVATED or BROKEN.");
        ClassLoader oldClassLoader = ClassLoaders.pushContextClassLoader(this.classLoader);
        bizState = BizState.DEACTIVATED;
        EventService eventService = ContainerHolder.getContainer().getService(
                EventService.class);
        try {
            // this can trigger uninstall handler
            eventService.sendEvent(new BeforeBizStopEvent(this));
        } finally {
            BizManagerService bizManagerService = ContainerHolder.getContainer()
                    .getService(BizManagerService.class);
            bizManagerService.unRegisterBiz(bizName, bizVersion);
            bizState = BizState.UNRESOLVED;
            eventService.sendEvent(new BeforeBizRecycleEvent(this));
            urls = null;
            denyImportPackages = null;
            denyImportClasses = null;
            denyImportResources = null;
            if (bizTempWorkDir != null && bizTempWorkDir.exists()) {
                bizTempWorkDir.delete();
            }
            bizTempWorkDir = null;
            classLoader = null;
            ClassLoaders.popContextClassLoader(oldClassLoader);
            eventService.sendEvent(new AfterBizStopEvent(this));
        }
    }

    @Override
    public void setCustomBizName(String bizName) {
        this.bizName = bizName;
    }

    @Override
    public BizState getBizState() {
        return bizState;
    }

    public BizModel setBizState(BizState bizState) {
        this.bizState = bizState;
        return this;
    }

    @Override
    public String getWebContextPath() {
        return webContextPath;
    }

    public BizModel setWebContextPath(String webContextPath) {
        this.webContextPath = (webContextPath == null ? Constants.ROOT_WEB_CONTEXT_PATH
                : webContextPath);
        return this;
    }

    @Override
    public Map<String, String> getAttributes() {
        return attributes;
    }

    public BizModel setAttributes(Map<String, String> attributes) {
        this.attributes.putAll(attributes);
        return this;
    }

    @Override
    public String toString() {
        return "Ark Biz: " + getIdentity();
    }

    private void resetProperties() {
        System.getProperties().remove("logging.path");
    }

    public File getBizTempWorkDir() {
        return bizTempWorkDir;
    }

    public BizModel setBizTempWorkDir(File bizTempWorkDir) {
        this.bizTempWorkDir = bizTempWorkDir;
        return this;
    }
}